#include "stdafx.h"
#include "cppflibinc.h"

using namespace cppflib;
using namespace cppflib::os;
using namespace cppflib::collections;
using namespace cppflib::threading;

static const int LOOPCOUNT = 1024 * 5;

static int Hello_A(ptr_t p)
{
   for (int i = 0; i < LOOPCOUNT; ++i) {
      if (i > 0 && (i % 100) == 0) Thread::Sleep(50);
         printf("\r%s = %d, param = %d | ", __FUNCTION__, i, p);
   }

   return 0;
}

static int Hello_B(ptr_t p)
{
   for (int i = 0; i < LOOPCOUNT; ++i) {
      if (i > 0 && (i % 100) == 0) Thread::Sleep(50);
      printf("\r                                        %s = %d, param = %d ", __FUNCTION__, i, p);
   }

   return 0;
}

static void TestThread_1(void)
{
   cppmempool::MemPoolWrap localPool;
   //
   // extra parentheses are needed or C++ compiler thinks I am declaring a function rather than a variable
   //
   Thread a((ThreadEntryPoint(&Hello_A)));
   Thread b((ThreadEntryPoint(&Hello_B)));

   a.Start(10);
   b.Start(334);

   assert(a.IsAlive());
   assert(b.IsAlive());

   a.Join();
   b.Join();

   debug::Print(_S("\nTesting thread wait ...\n"));
   Thread::Sleep(500);

}

static void SetAndRemoveNamedData(pcwstr_t customString)
{
   Thread::SetNamedData(CFString("what"), new CFString(customString));
   BaseObject *pObj = Thread::GetNamedData(CFString("what"));
   assert(pObj != NULL);
   CFString *pStr = static_cast<CFString*>(pObj);
   assert(*pStr == customString);

   assert(Thread::RemoveNamedData(CFString("what")));
   assert(!Thread::RemoveNamedData(CFString("qwerty"))); // not exist

   assert(Thread::GetNamedData(CFString("what")) == NULL); // should not exist
}

static void GetAndSetNamedData(pcwstr_t customString)
{
   cppmempool::MemPoolWrap localPool;

   Thread::SetNamedData(CFString("slot1"), new CFString(customString));
   Thread::Sleep(100);
   BaseObject *pObj = Thread::GetNamedData(CFString("slot1"));
   assert(pObj != NULL);
   CFString *pStr = static_cast<CFString*>(pObj);
   assert(*pStr == customString);
}

static int Hello_2A(ptr_t p)
{
   cppmempool::MemPoolWrap localPool;
   assert(Thread::GetNamedData(CFString("slot1")) == NULL);
   
   GetAndSetNamedData(_S("hello my 2A"));
   SetAndRemoveNamedData(_S("123456"));

   return 0;
}

static int Hello_2B(ptr_t p)
{
   cppmempool::MemPoolWrap localPool;
   assert(Thread::GetNamedData(CFString("slot1")) == NULL);

   GetAndSetNamedData(_S("___hello my 2B____"));
   SetAndRemoveNamedData(_S("000000000"));

   return 0;
}



static void TestThreadLocalStorage(void)
{
   debug::Print(debug::DL_DEBUG, _S("Testing thread local storage...\n"));

   cppmempool::MemPoolWrap localPool;
   assert(Thread::GetNamedData(CFString("slot1")) == NULL);

   Thread a((ThreadEntryPoint(&Hello_2A)));
   Thread b((ThreadEntryPoint(&Hello_2B)));

   a.Start(10);
   b.Start(334);

   assert(a.IsAlive());
   assert(b.IsAlive());

   GetAndSetNamedData(_S("___hello I am in main thread____"));

   a.Join();
   b.Join();

   SetAndRemoveNamedData(_S("dkdkdkddk"));
}

static const int ARRAYCOUNT = 1024 * 128;
static ArrayCppObjT<CFString> *pArray;


static int ThreadRelease1(ptr_t p)
{
   Thread *pTh = reinterpret_cast<Thread*>(p);

   debug::Print(debug::DL_DEBUG, _S("%S -- thread id = 0x%x, curThreadId = 0x%x\n"), __FUNCTION__, pTh->GetId(), Thread::GetCurrentThreadId());   
   assert(pTh->GetId() == Thread::GetCurrentThreadId());

   for (int i = 0; i < ARRAYCOUNT; ++i) {
      assert(cppmempool::Release(pArray->Get(i)) > 0);
   }

   return 0;
}


static int ThreadRelease2(ptr_t p)
{
   Thread *pTh = reinterpret_cast<Thread*>(p);

   debug::Print(debug::DL_DEBUG, _S("%S -- thread id = 0x%x, curThreadId = 0x%x\n"), __FUNCTION__, pTh->GetId(), Thread::GetCurrentThreadId());
   assert(pTh->GetId() == Thread::GetCurrentThreadId());

   for (int i = 0; i < ARRAYCOUNT; ++i) {
      assert(cppmempool::Release(pArray->Get(i)) > 0);
   }

   return 0;
}

/**
 *  To test whether memory allocated in one thread and released in another
 *  will not make any problem 
 */
static void TestAllocRelease(void)
{
   debug::Print(_S("Testing alloc/release ...\n"));

   cppmempool::MemPoolWrap localPool;

   pArray = new ArrayCppObjT<CFString>(ARRAYCOUNT);
   
   for (int i = 0; i < ARRAYCOUNT; ++i) {
      CFString * str = new CFString("ffff");
      pArray->Set(i, str);
      assert(cppmempool::Retain(str) == 3);
   }

   Thread a((ThreadEntryPoint(&ThreadRelease1)));
   Thread b((ThreadEntryPoint(&ThreadRelease2)));

   a.Start(reinterpret_cast<ptr_t>(&a));
   b.Start(reinterpret_cast<ptr_t>(&b));
   
   a.Join();
   b.Join();

   debug::Print(_S("release thread ends ...\n"));

   /*
    *  If 'Release' is not run in a critical section, the following assert may fail.
    *  Some 'Release' may be missed in the 2 threads above, which leads to 'ret' be 1 below !
    */
   for (int j = 0; j < ARRAYCOUNT; ++j) {
      int ret = cppmempool::Release(pArray->Get(j));
      assert(ret == 0);
      pArray->DirectSet(j, NULL);
   }

   debug::Print(_S("all strings released ...\n"));

}

static const int INCREMENTCOUNT = 1024 * 256;
static int g_value = 0;
static int g_value_2 = 0;

static int ThreadIncrement1(ptr_t p)
{
   SyncLock *pLock = reinterpret_cast<SyncLock*>(p);

   for (int i = 0; i < INCREMENTCOUNT; ++i) {
      SyncWrap<SyncLock> wrap(pLock);
      g_value += 2;
   }

   for (int i = 0; i < INCREMENTCOUNT; ++i) {
      SyncWrap<SyncLock> wrap(pLock);
      g_value_2 += 3;
   }

   return 0;
} 

static int ThreadIncrement2(ptr_t p)
{
   SyncLock *pLock = reinterpret_cast<SyncLock*>(p);

   for (int i = 0; i < INCREMENTCOUNT; ++i) {
      SyncWrap<SyncLock> wrap(pLock);
      g_value += 2;
   }

   for (int i = 0; i < INCREMENTCOUNT; ++i) {
      SyncWrap<SyncLock> wrap(pLock);
      g_value_2 += 3;
   }

   return 0;
}


static void TestSyncLock(void)
{
   cppmempool::MemPoolWrap localPool;

   debug::Print(_S("Testing SyncLock ...\n"));

   Thread a((ThreadEntryPoint(&ThreadIncrement1)));
   Thread b((ThreadEntryPoint(&ThreadIncrement2)));
   SyncLock myLock;

   a.Start(reinterpret_cast<ptr_t>(&myLock));
   b.Start(reinterpret_cast<ptr_t>(&myLock));

   assert(a.IsAlive());
   assert(b.IsAlive());

   a.Join();
   b.Join();

   assert(g_value == INCREMENTCOUNT * 2 * 2);
   assert(g_value_2 == INCREMENTCOUNT * 3 * 2);
}

static int g_semValue = 0;
static bool volatile g_cont = true;
static const int KCONST_INT = 3;
static Semaphore *startSem = NULL;

static int WaitSemaphoreThread(ptr_t p)
{
   Semaphore **semArray = reinterpret_cast<Semaphore**>(p);
   Semaphore *pSem = semArray[0];
   Semaphore *pSem2 = semArray[1];
   g_semValue = 0;
   int c = 0;

   startSem->Post();

   while (g_cont) {
      //printf("WaitSemaphoreThread %d\n", c++);
      pSem->Wait();
      g_semValue += KCONST_INT;
      pSem2->Post();
      //printf("WaitSemaphoreThread 2 ...\n");

   }

   return 0;
}

static const int SEMLOOPCOUNT = 20;

static int PostSemaphoreThread(ptr_t p)
{
   Semaphore **semArray = reinterpret_cast<Semaphore**>(p);
   Semaphore *pSem = semArray[0];
   Semaphore *pSem2 = semArray[1];
   int c = 0;

   startSem->Wait();

   for (int i = 0; i < SEMLOOPCOUNT; ++i) {
      //printf("PostSemaphoreThread %d\n", c++);
      if (i == SEMLOOPCOUNT - 1) // ending condition must be put here
         g_cont = false;
      pSem->Post();
      //printf("PostSemaphoreThread 2 ...\n");
      pSem2->Wait();
      
   }

   return 0;
}

static void TestUnnamedSemaphore(void)
{
   cppmempool::MemPoolWrap localPool;

   debug::Print(_S("Testing Unnamed Semaphore ...\n"));

   startSem = new Semaphore(1);

   Thread a((ThreadEntryPoint(&WaitSemaphoreThread)));
   Thread b((ThreadEntryPoint(&PostSemaphoreThread)));
   Semaphore mySem(1);  // an unnamed semaphore
   Semaphore mySem2(1);  // an unnamed semaphore
   Semaphore *semArray[2] = {&mySem, &mySem2};

   a.Start(reinterpret_cast<ptr_t>(semArray));
   b.Start(reinterpret_cast<ptr_t>(semArray));

   assert(a.IsAlive());
   assert(b.IsAlive());

   a.Join();
   b.Join();

   debug::Print(debug::DL_DEBUG, _S("g_semValue = %d, expected = %d\n"), g_semValue, KCONST_INT * SEMLOOPCOUNT);
   mySem.Close();
   mySem2.Close();
   startSem->Close();
   assert(g_semValue == (KCONST_INT * SEMLOOPCOUNT));
}

static const int NAMEDSEMLOOPCOUNT = 10;

static void TestNamedSemaphore(void)
{
   cppmempool::MemPoolWrap localPool;

   debug::Print(_S("Testing Named Semaphore ...\n"));

   Semaphore *pSem = new Semaphore(_S("hello_world"), 1);
   Semaphore *pSem2 = new Semaphore(_S("hello_world2"), 1);
   Semaphore *pEndSem = new Semaphore(_S("endloop"), 1);

   assert(pSem->IsFirstInstance());
   assert(pEndSem->IsFirstInstance());

   debug::Print(_S("Launching Testcppflib_2.exe ...\n"));

#if defined(WIN32)
   ListT<pcwstr_t> argList;
   argList.AddTail(_S("-tnamedsem"));
   Process *pProc = Process::RunProc(_S("Testcppflib_2.exe"), argList);
#elif defined(linux)
   pcwstr_t procName = 
#if defined(DEBUG) && defined(USE_CPPFLIBSO)
                       _S("../bin_Testcppflib_2_so_debug/Testcppflib_2");
#elif defined(DEBUG) && !defined(USE_CPPFLIBSO)
                       _S("../bin_Testcppflib_2_debug/Testcppflib_2");
#elif !defined(DEBUG) && defined(USE_CPPFLIBSO)
                       _S("../bin_Testcppflib_2_so/Testcppflib_2");
#else
                       _S("../bin_Testcppflib_2/Testcppflib_2");
#endif

   Process *pProc = Process::RunProc(procName, _S("-tnamedsem"));
#endif
   assert(pProc != NULL);

   g_semValue = 0;

   while (!pEndSem->Wait(0)) {
      pSem->Wait();
      g_semValue += (KCONST_INT + 2);
      pSem2->Post();
   }

   debug::Print(debug::DL_DEBUG, _S("g_semValue = %d, expected = %d\n"), g_semValue, (KCONST_INT + 2)  * NAMEDSEMLOOPCOUNT);
   assert(g_semValue == (KCONST_INT + 2)  * NAMEDSEMLOOPCOUNT);

   u32_t exitCode = 0;
   assert(pProc->WaitForExit(exitCode));
   assert(exitCode == 123);
}

static void TestLaunchProcess(void)
{
   cppmempool::MemPoolWrap localPool;

   debug::Print(_S("Launching Testcppflib_2.exe ...\n"));

   Process *pProc = Process::RunProc(_S("Testcppflib_2"));
   assert(pProc != NULL);

   u32_t exitCode = 0;
   pProc->WaitForExit(exitCode);
   assert(exitCode == 123);
}

static int EventThread(ptr_t p)
{
   Event *pEvent = reinterpret_cast<Event*>(p);

   Thread::Sleep(500);

   pEvent->Set();
   
   return 0;
}

void TestEvent(void)
{
   cppmempool::MemPoolWrap localPool;

   debug::Print(_S("Testing event ...\n"));

   Thread a((ThreadEntryPoint(&EventThread)));
   Event *myEvent = new Event();  // a event

   a.Start(reinterpret_cast<ptr_t>(myEvent));
   myEvent->Wait();
   
   a.Join();
}

void TestThread(bool isFullTest)
{
   debug::Print(_S("Testing threads ...\n"));

   debug::Print(debug::DL_DEBUG, _S("%S -- current thread id = 0x%x\n"), __FUNCTION__, Thread::GetCurrentThreadId());

   TestThreadLocalStorage();

   if (isFullTest) {
      TestThread_1();
      TestAllocRelease();
      TestSyncLock();
      TestUnnamedSemaphore();
#if defined(__ANDROID__)
      // named semaphore (sem_open) not working on Android at present
      TestLaunchProcess();
#else
      TestNamedSemaphore();
#endif
   }

   TestEvent();
}

